package in.srain.cube.request;

import android.text.TextUtils;

import java.io.File;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;

import in.srain.cube.request.progress.ProgressListener;

public class RequestData implements Serializable {

    public static final String GET = "GET";
    public static final String POST = "POST";
    public static final String DELETE = "DELETE";
    public static final String PATCH = "PATCH";
    public static final String PUT = "PUT";

    public static final String EMPTY = "";
    public static final String CHAR_QM = "?";
    public static final String CHAR_AND = "&";
    public static final String CHAR_EQUALS = "=";
    public static final String CHAR_SET_UTF_8 = "UTF-8";
    public String mUrl;
    private HashMap<String, Object> mQueryData;
    private HashMap<String, Object> mPostData;
    private HashMap<String, Object> mHeaderData;
    private HashMap<String, UploadFileInfo> mUploadFileInfoHashMap;

    private String mTag;
    private String mMethod = GET;

    private String mContentType;
    private String mRequestBody;

    private ProgressListener mProgressListener;

    public static String buildQueryString(Map<String, ?> data, String url) {
        return buildQueryString(data, url, true);
    }

    public static Map<String, String> parseQuery(String query) {
        if (TextUtils.isEmpty(query)) {
            return null;
        }
        Map<String, String> map = new LinkedHashMap<String, String>();
        String[] pairs = query.split(CHAR_AND);
        for (String pair : pairs) {
            int idx = pair.indexOf(CHAR_EQUALS);
            try {
                map.put(URLDecoder.decode(pair.substring(0, idx), CHAR_SET_UTF_8), URLDecoder.decode(pair.substring(idx + 1), CHAR_SET_UTF_8));
            } catch (UnsupportedEncodingException e) {
                // OK to catch UnsupportedEncodingException here
                e.printStackTrace();
            }
        }
        return map;
    }

    public static String buildQueryString(Map<String, ?> data, String url, boolean urlEncode) {

        if (data == null || data.size() == 0) {
            return url;
        }

        StringBuilder sb = new StringBuilder();
        boolean append = false;
        if (url != null) {
            sb.append(url);
            if (url.contains(CHAR_QM)) {
                append = true;
            } else {
                sb.append(CHAR_QM);
            }
        }
        Iterator<? extends Map.Entry<String, ?>> it = data.entrySet().iterator();
        while (it.hasNext()) {
            Map.Entry<String, ?> item = it.next();
            if (append) {
                sb.append(CHAR_AND);
            } else {
                append = true;
            }

            try {
                if (TextUtils.isEmpty(item.getKey())) {
                    continue;
                }
                sb.append(urlEncode ? URLEncoder.encode(item.getKey(), CHAR_SET_UTF_8) : item.getKey());
                sb.append(CHAR_EQUALS);
                if (item.getValue() != null) {
                    sb.append(urlEncode ? URLEncoder.encode(item.getValue().toString(), CHAR_SET_UTF_8) : item.getValue().toString());
                }
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
        return sb.toString();
    }

    public RequestData addHeader(Map<String, ?> header) {
        if (header == null) {
            return this;
        }
        if (mHeaderData == null) {
            mHeaderData = new HashMap<String, Object>();
        }
        mHeaderData.putAll(header);
        return this;
    }

    public RequestData addPostData(String key, Object data) {
        if (mPostData == null) {
            mPostData = new HashMap<String, Object>();
        }
        method(POST);
        mPostData.put(key, data);
        return this;
    }

    public RequestData addHeader(String key, Object data) {
        if (mHeaderData == null) {
            mHeaderData = new HashMap<String, Object>();
        }
        mHeaderData.put(key, data);
        return this;
    }

    public RequestData addPostData(Map<String, ?> data) {
        if (mPostData == null) {
            mPostData = new HashMap<String, Object>();
        }
        method(POST);
        mPostData.putAll(data);
        return this;
    }

    public RequestData addQueryData(String key, Object data) {
        if (mQueryData == null) {
            mQueryData = new HashMap<String, Object>();
        }
        mQueryData.put(key, data);
        return this;
    }

    public RequestData addQueryData(Map<String, ?> data) {
        if (mQueryData == null) {
            mQueryData = new HashMap<String, Object>();
        }
        mQueryData.putAll(data);
        return this;
    }

    public String getRequestUrl() {
        if (mQueryData != null) {
            return buildQueryString(mQueryData, mUrl);
        }
        return mUrl;
    }

    public RequestData setRequestUrl(String url) {
        mUrl = url;
        return this;
    }

    public HashMap<String, Object> getHeaderData() {
        return mHeaderData;
    }

    public HashMap<String, Object> getQueryData() {
        return mQueryData;
    }

    public HashMap<String, Object> getPostData() {
        return mPostData;
    }

    @Deprecated
    public RequestData usePost(boolean use) {
        mMethod = use ? POST : GET;
        return this;
    }

    /**
     * add file to be uploaded
     *
     * @param fieldName
     * @param uploadFile, will extract extension from the file name
     * @return
     */
    @SuppressWarnings({"unused"})
    public RequestData addFile(String fieldName, String uploadFile) {
        method(POST);
        addFile(fieldName, uploadFile, null);
        return this;
    }

    /**
     * add file to be uploaded
     *
     * @param fieldName
     * @param uploadFile
     * @param fileName   if provided, will use this as filename
     * @return
     */
    @SuppressWarnings({"unused"})
    public RequestData addFile(String fieldName, String uploadFile, String fileName) {
        addFile(fieldName, new File(uploadFile), fileName);
        return this;
    }

    /**
     * @param fieldName
     * @param uploadFile
     * @return
     */
    @SuppressWarnings({"unused"})
    public RequestData addFile(String fieldName, File uploadFile) {
        addFile(fieldName, uploadFile, null);
        return this;
    }

    @SuppressWarnings({"unused"})
    public RequestData addFile(String fieldName, File uploadFile, String fileName) {

        if (mUploadFileInfoHashMap == null) {
            mUploadFileInfoHashMap = new HashMap<String, UploadFileInfo>();
        }

        UploadFileInfo uploadFileInfo = new UploadFileInfo();
        uploadFileInfo.fieldName = fieldName;
        uploadFileInfo.uploadFile = uploadFile;
        uploadFileInfo.fileName = fileName;

        mUploadFileInfoHashMap.put(fieldName, uploadFileInfo);
        return this;
    }

    @SuppressWarnings({"unused"})
    public RequestData setProgressListener(ProgressListener progressListener) {
        this.mProgressListener = progressListener;
        return this;
    }

    public RequestData method(String method) {
        mMethod = method;
        return this;
    }

    public String getMethod() {
        return mMethod;
    }

    public String getTag() {
        return mTag;
    }

    /**
     * Set a tag to mark this request
     */
    public RequestData setTag(String tag) {
        mTag = tag;
        return this;
    }

    public String getPostString() {
        if (mPostData == null || mPostData.size() == 0) {
            return EMPTY;
        }
        return buildQueryString(mPostData, null);
    }

    public boolean isGetRequest() {
        return mMethod == GET && ((mPostData != null && mPostData.size() > 0) || isMultiPart() || !TextUtils.isEmpty(mContentType));
    }

    public RequestData setRequestBody(String contentType, String requestBody) {
        mContentType = contentType;
        mRequestBody = requestBody;
        return this;
    }

    public String getRequestBody() {
        return mRequestBody;
    }

    public String getContentType() {
        return mContentType;
    }

    public HashMap<String, UploadFileInfo> getUploadFiles() {
        return mUploadFileInfoHashMap;
    }

    public ProgressListener getProgressListener() {
        return mProgressListener;
    }

    public boolean isMultiPart() {
        return mUploadFileInfoHashMap != null && mUploadFileInfoHashMap.size() > 0;
    }

    @Override
    public String toString() {
        return String.format("URL=%s \n Method=%s \n Headers=%s \n Query=%s \n Post=%s \n Upload=%s \n ContentType=%s \n RequestBody=%s", getRequestUrl(), mMethod, mHeaderData, mQueryData, mPostData, mUploadFileInfoHashMap, mContentType, mRequestBody);
    }

    public String toCurl() {
        StringBuilder sb = new StringBuilder();
        sb.append("curl -q");
        return sb.toString();
    }

    public static class UploadFileInfo implements Serializable {
        public File uploadFile;
        public String fileName;
        public String fieldName;

        @Override
        public String toString() {
            return String.format("UploadFileInfo: [field: %s, file name: %s, file: %s]", fieldName, fileName, uploadFile);
        }
    }
}
